package fr.miage.course7.gps;

import java.io.IOException;
import java.net.ServerSocket;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.TreeSet;

/**
 * Classe gérant le serveur.
 * @author Jonathan LAMOTTE
 * @author Mickael LAROCHE
 *
 */
public class Serveur {

    /**
     * Socket du serveur.
     */
    private ServerSocket ss = null;

    /**
     * Thread d'acceptation des connexions.
     */
    private Thread t;

    /**
     * Liste des sockets clients.
     */
    private List<Communication> listeClients;

    /**
     * Generateur de la course.
     */
    private CourseGenerateur generateur;

    /**
     * Liste des coordonnées des continents.
     */
    private static ArrayList<ArrayList<Position>> continents
                                         = new ArrayList<ArrayList<Position>>();
    /**
     * Liste des coordonnées non autorisées.
     */
    private static ArrayList<TreeSet<Integer>> coordInterdites
                                            = new ArrayList<TreeSet<Integer>>();
    /**
     * Port d'écoute du serveur.
     */
    private final int port = 5000;

    /**
     * Temps d'attente entre 2 transmissions.
     */
    private final long sleeptime = 10000; //En millisecondes

    /**
     * Variable de fin de boucle.
     */
    private boolean fin = false;

	private ArrayList<Position> itineraire;

	private ArrayList<Position> etapes;


    /**
     * Constructeur.
     */
    public Serveur() {

        try {
        	//Initialisation de la liste des etapes
        	etapes = new ArrayList<Position>();
        	
        	//Initialisation de l'itineraire
        	itineraire = new ArrayList<Position>();
        	
            //Initialisation de la liste des clients
            listeClients = new ArrayList<Communication>();

            ss = new ServerSocket(port);
            System.out.println("Le serveur est à l'écoute du port " + port);

            t = new Thread(new AccepterConnexion(ss, this));
            t.start();

            //Creation de la course
            
          //definition de quelques coordonnees d'etapes
            Position depart = nouvelleEtape(+46, -5);
            Position etape = nouvelleEtape(-60, -75);
            Position arrivee = nouvelleEtape(-40, +16);

          //definition de l'itineraire correspondant
            itineraire.add(depart);
            itineraire.add(new Position(+46, -35));
            itineraire.add(new Position(-10, -25));
            itineraire.add(new Position(-40, -40));
            itineraire.add(etape);
            itineraire.add(new Position(-55, -120));
            itineraire.add(new Position(-55, -180));
            itineraire.add(new Position(-53, 140));
            itineraire.add(new Position(-50, 90));
            itineraire.add(new Position(-48, 30));
            itineraire.add(arrivee);
            
            
            generationContinents();
            generationCoordInter();
            generateur = new CourseGenerateur(coordInterdites, etapes, itineraire);

            while (!fin) {
                try {
                    Thread.sleep(sleeptime);

                    generateur.nouvelleTransmissionAleatoire();

                    updateAll();

                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }


        } catch (IOException e) {
            System.err.println("Le port 5000 est déjà utilisé !");
        }

    }


    /**
     * Cree une nouvelle etape et l'ajoute a la liste.
     * @param lat la latitude de l'etape
     * @param lon la longitude de l'etape
     * @return l'etape creee
     */
    public final Position nouvelleEtape(final int lat, final int lon) {
        Position temp = new Position(lat, lon);
        etapes.add(temp);
        return temp;
    }

	/**
     * Ajoute un client.
     * @param c client
     */
    public final void ajoutClient(final Communication c) {
        listeClients.add(c);
    }

    /**
     * Iterateur sur la liste des clients.
     * @return un iterateur sur la liste des clients
     */
    public final Iterator<Communication> iteratorClients() {
        return listeClients.iterator();
    }

    /**
     * Retire un client de la liste.
     * @param c client
     */
    public final void remove(final Communication c) {
        listeClients.remove(c);
    }

    /**
     * Envoie un message de mise a jour a tout les clients.
     */
    public final void updateAll() {
        Iterator<Communication> iter = iteratorClients();

        while (iter.hasNext()) {
            iter.next().updateClient();
        }

      //re-affichage des participants
        Iterator<Participant> iterPart = iteratorParticipants();
        while (iterPart.hasNext()) {
            System.out.println(iterPart.next());
        }
    }

    /**
     * Genere et enregistre la liste des extremites des continents.
     */
    public static void generationContinents() {
        /* Regles pour les continents :
         * En un seul morceau sur la carte
         * Pas de pointe seule (un trait de deux points)
         * Un trait horizontal (hors pointe)
         * doit etre fait en deux etapes consecutives
        */
        ArrayList<Position> continent = new ArrayList<Position>();

        //Russie ouest
        continent.add(new Position(+80, -180));
        continent.add(new Position(+60, -180));
        continent.add(new Position(+60, -171));
        continent.add(new Position(+80, -171));
        continent.add(new Position(+80, -180));
        continents.add(continent);

        //Amerique
        continent = new ArrayList<Position>();
        continent.add(new Position(+80, -169));
        continent.add(new Position(+50, -169));
        continent.add(new Position(+50, -140));
        continent.add(new Position(+50, -139));
        continent.add(new Position(0, -85));
        continent.add(new Position(-60, -75));
        continent.add(new Position(-60, -65));
        continent.add(new Position(-5, -30));
        continent.add(new Position(+15, -65));
        continent.add(new Position(+30, -65));
        continent.add(new Position(+65, -30));
        continent.add(new Position(+80, -10));
        continent.add(new Position(+80, -169));
        continents.add(continent);

        //Eurasie-Afrique-Oceanie
        continent = new ArrayList<Position>();
        continent.add(new Position(+80, 0));
        continent.add(new Position(+50, -15));
        continent.add(new Position(+46, -5));
        continent.add(new Position(+10, -25));
        continent.add(new Position(0, -10));
        continent.add(new Position(0, +5));
        continent.add(new Position(-40, +15));
        continent.add(new Position(-40, +16));
        continent.add(new Position(-30, +50));
        continent.add(new Position(-10, +55));
        continent.add(new Position(0, +55));
        continent.add(new Position(0, +90));
        continent.add(new Position(-40, +115));
        continent.add(new Position(-45, +154));
        continent.add(new Position(-45, +155));
        continent.add(new Position(0, +155));
        continent.add(new Position(0, +156));
        continent.add(new Position(+20, +130));
        continent.add(new Position(+60, +180));
        continent.add(new Position(+80, +180));
        continent.add(new Position(+80, 0));
        continents.add(continent);

        //Antarctique
        continent = new ArrayList<Position>();
        continent.add(new Position(-70, -180));
        continent.add(new Position(-90, -180));
        continent.add(new Position(-90, +180));
        continent.add(new Position(-70, +180));
        continent.add(new Position(-70, -180));
        continents.add(continent);
    }

    /**
     * Genere et enregistre la liste des extremites des coordonnees interdites.
     * Les continents doivent avoir ete generes au prealable
     */
    public static void generationCoordInter() {
        for (int i = 0; i <= 180; ++i) {
            coordInterdites.add(new TreeSet<Integer>());
        }
        for (int ci = 0; ci < continents.size(); ++ci) {
            ArrayList<Position> c = continents.get(ci);

          //vers la gauche ou la droite
            boolean aDroite;

          //latitude des points 1 et 2 et longitude des points 1 et 2
            int lat1, lat2, lon1, lon2;

          //coefficient directeur de la droite formee par les points 1 et 2
            float coeffDirect;

            for (int i = 0; i < c.size() - 1; ++i) {
                lat1 = c.get(i).getLat();
                lon1 = c.get(i).getLon();
                lat2 = c.get(i + 1).getLat();
                lon2 = c.get(i + 1).getLon();
                //System.out.println("lat1 :" + lat1 + " lat2 :" + lat2
//                                 + " lon1 :" + lon1 + " lon2 :" + lon2);

                if (lat1 == lat2) {
                    coordInterdites.get(latToInd(lat1)).add(lon1);
                    coordInterdites.get(latToInd(lat2)).add(lon2);
                    //System.out.println("lat1=lat2");
                } else {
                    aDroite = lat1 > lat2;
                    coeffDirect = (float) (lon2 - lon1) / (float) (lat2 - lat1);
                    if (aDroite) {
//                            System.out.println("CD = " + coeffDirect);
//                            System.out.println("lon2-lon1 =" + (lon2-lon1));
//                            System.out.println("lat2-lat1 =" + (lat2-lat1));
//                            System.out.println("ladiv = "
//                                                 + ((lon2-lon1)/(lat2-lat1)));

                        for (int j = lat1; j >= lat2; --j) {
                            coordInterdites.get(latToInd(j))
                                .add((int) Math.floor(
                                                lon1 + (j - lat1) * coeffDirect)
                                           );
                            //System.out.println("j="  + j
//                                               + " / ajout aDroite :"
//                                               + (int)Math.floor(
//                                                   lon1+(j-lat1)
//                                                    *coeffDirect)
//                                                    );
                        }
                    } else {
                        for (int j = lat1; j <= lat2; ++j) {
                            coordInterdites.get(latToInd(j)).add(
                                            (int) Math.ceil(lon1
                                                     + (j - lat1) * coeffDirect)
                                                   );

                            //System.out.println("ajout aGauche :"
//                                              + (int) Math.ceil(
//                                                     lon1
//                                                   + (j - lat1) * coeffDirect)
//                                                      );
                        }
                    }
                }
            }
        }

        //System.out.println(coordInterdites);

//Test pour verifier la conformite des continents
//        int ok=-1;
//        for (int c=0;c<coordInterdites.size();c++) {
//            TreeSet<Integer> d = coordInterdites.get(c);
//            if ((d.size()%2)!=0) {
//                System.out.println("dsize="+ d.size());
//                ok = c;
//                break;
//            }
//        }
//        System.out.println("ok ="  +ok);
    }

    /**
     * Retourne la valeur de l'indice du tableau correspondant à la latitude.
     * @param coord la latitude
     * @return la valeur de l'indice du tableau correspondant à la latitude
     */
    static int latToInd(final int coord) {
        final int convertisseur = 90;

        return coord + convertisseur;
    }

    /**
     * Retourne la valeur de la latitude correspondant à l'indice du tableau.
     * @param coord l'indice
     * @return la valeur de la latitude correspondant à l'indice du tableau
     */
    static int indToLat(final int coord) {
        final int convertisseur = 90;

        return coord - convertisseur;
    }

    /**
     * Retourne un iterator sur la liste des participants.
     * @return un iterator sur la liste des participants
     */
    public final Iterator<Participant> iteratorParticipants() {
        return generateur.getParticipants().values().iterator();
    }

    /**
     * Retourne la liste des extremites des coordonnees interdites.
     * @return la liste des extremites des coordonnees interdites
     */
    public static ArrayList<TreeSet<Integer>> getCoordInterd() {
        return coordInterdites;
    }

    public String etapesToXML() {
    	Iterator<Position> iterPos = etapes.iterator();
    	Position pos;
    	String xml = "<etapes>";

    	while(iterPos.hasNext()) {
    		pos = iterPos.next();

    		xml = xml.concat("<etape lat='" + pos.getLat() + "' "
    				       + "lon='" + pos.getLon() + "' />");
    	}

    	xml = xml.concat("</etapes>");

    	return xml;

    }

    public String itineraireToXML() {
    	Iterator<Position> iterI = itineraire.iterator();
    	Position itineraire;
    	String xml = "<itineraire>";

    	while(iterI.hasNext()) {
    		itineraire = iterI.next();

    		xml = xml.concat("<etape lat='" + itineraire.getLat() + "' "
    				       + "lon='" + itineraire.getLon() + "' />");
    	}

    	xml = xml.concat("</itineraire>");

    	return xml;
    }
}
